package org.apache.ocean.main.http;

import java.io.IOException;
import java.io.StringReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.ocean.main.client.OceanXML;
import org.apache.ocean.main.client.OceanXML.RequestException;

public class MulticastHttp {
	private ExecutorService threadPool;

	public static enum ExceptionEnum {
		ALL, NONE, ONE
	};

	private ExceptionEnum exceptionEnum;

	public MulticastHttp(ExecutorService threadPool, ExceptionEnum exceptionEnum) {
		this.threadPool = threadPool;
		this.exceptionEnum = exceptionEnum;
	}

	public static class AllExceptions extends Exception {
		private List<HttpRequestException> exceptions;

		public AllExceptions(List<HttpRequestException> exceptions) {
			this.exceptions = exceptions;
		}

		public List<HttpRequestException> getExceptions() {
			return exceptions;
		}
	}

	public static class HttpRequestException extends Exception {
		private URL url;

		public HttpRequestException(URL url, Throwable throwable) {
			this.url = url;
			initCause(throwable);
		}
		
		public URL getUrl() {
			return url;
		}
	}

	public class HttpTask implements Callable<OceanXML> {
		private HttpRequest httpRequest;

		public HttpTask(HttpRequest httpRequest) {
			this.httpRequest = httpRequest;
		}

		public OceanXML call() throws Exception {
			return OceanXML.request(httpRequest.url, httpRequest.parameters, httpRequest.httpTimeout);
		}
	}

	public class PostTask implements Callable<OceanXML> {
		private URL url;
		private RequestEntity requestEntity;
		private HttpTimeout defaultHttpTimeout;

		public PostTask(URL url, RequestEntity requestEntity, HttpTimeout defaultHttpTimeout) {
			this.url = url;
			this.requestEntity = requestEntity;
			this.defaultHttpTimeout = defaultHttpTimeout;
		}

		public OceanXML call() throws Exception {
			try {
				PostMethod postMethod = new PostMethod(url.toString());
				HttpClient httpClient = new HttpClient();
				httpClient.getParams().setSoTimeout(1000);
				httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(1000);
				httpClient.executeMethod(postMethod);
				int status = postMethod.getStatusCode();
				String responseString = postMethod.getResponseBodyAsString();
				if (status == 200) {
					return OceanXML.handle(new StringReader(responseString));
				} else {
					throw new Exception(responseString);
				}
			} catch (Throwable throwable) {
				throw new HttpRequestException(url, throwable);
			}
		}
	}

	public List<OceanXML> executePost(Collection<URL> urls, HttpTimeout defaultHttpTimeout, RequestEntity requestEntity) throws AllExceptions, RequestException, IOException, InterruptedException {
		List<Callable<OceanXML>> callables = new ArrayList<Callable<OceanXML>>();
		for (URL url : urls) {
			PostTask postTask = new PostTask(url, requestEntity, defaultHttpTimeout);
			callables.add(postTask);
		}
		return execute(callables);
	}
  
	public List<OceanXML> execute(Collection<URL> urls, HttpTimeout defaultHttpTimeout) throws AllExceptions, RequestException, IOException, InterruptedException {
		return execute(urls, (HttpParameters)null, defaultHttpTimeout);
	}
	
	public List<OceanXML> execute(Collection<URL> urls, HttpParameters httpParameters, HttpTimeout defaultHttpTimeout) throws AllExceptions, RequestException, IOException, InterruptedException {
		List<Callable<OceanXML>> callables = new ArrayList<Callable<OceanXML>>();
		for (URL url : urls) {
			HttpRequest httpRequest = new HttpRequest();
			httpRequest.url = url;
			httpRequest.parameters = httpParameters;
			httpRequest.httpTimeout = defaultHttpTimeout;
			callables.add(new HttpTask(httpRequest));
		}
		return execute(callables);
	}

	public List<OceanXML> execute(List<Callable<OceanXML>> callables) throws AllExceptions, RequestException, IOException, InterruptedException {
		List<OceanXML> responses = new ArrayList<OceanXML>();
		List<Future<OceanXML>> futures = threadPool.invokeAll(callables);
		List<HttpRequestException> exceptions = new ArrayList<HttpRequestException>();
		for (Future<OceanXML> future : futures) {
			try {
				OceanXML response = future.get();
				responses.add(response);
			} catch (ExecutionException executionException) {
				if (exceptionEnum.equals(ExceptionEnum.ONE)) {
					throwException(executionException);
				} else if (exceptionEnum.equals(ExceptionEnum.ALL)) {
					HttpRequestException httpRequestException = (HttpRequestException)executionException.getCause();
					exceptions.add(httpRequestException);
				}
			}
		}
		if (exceptions.size() > 0) {
			throw new AllExceptions(exceptions);
		}
		return responses;
	}

	private void throwException(ExecutionException executionException) throws IOException, RequestException {
		Throwable throwable = executionException.getCause();
		if (throwable instanceof IOException) {
			throw (IOException) throwable;
		} else if (throwable instanceof RequestException) {
			throw (RequestException) throwable;
		} else {
			throw new RuntimeException(throwable);
		}
	}
}
